Custom Integrations

Custom integrations are a powerful feature in Downie that allows you to add custom handlers for sites that are not supported. It requires some knowledge of JavaScript which is used in context of the webpage.

When you add an integration, first you need to define regex for links that are handled by this integration. For example, if you for want to support links such as https://www.youtube.com/watch?v=MzPECgrwjKE, you would use a regex similar to https?://www\.youtube\.com/watch\?v=[\w_-]+. For more information about regex, please see e.g. https://en.wikipedia.org/wiki/Regular_expression but there are many other resources online.

Optionally, you can define an identifier within the regex using a named group called ID. Identifiers help Downie when looking for duplicate results in case it is extracting links from an unknown source. The identifier must not be empty and should uniquely identify the content it points to. In case of the YouTube example above, it’s the video ID that is the v query parameter value. To add the named group, surround the ID part with (?P<ID>id_regex) - i.e. https?://www\.youtube\.com/watch\?v=(?P<ID>[\w_-]+). Google for named regex groups for more information.

Once you have a regex filter in place, you need to define the JavaScript code. The code is loaded after a web view component loads the webpage - similar to when you write something into a console in the web inspector in Safari. What you do here is completely up to you.

For communicating with Downie, there are 3 methods:

1. Reporting a direct download

Use window.downie.reportDownload(download) to report to Downie that you’ve found a direct download. Direct download is a link that leads directly to a file (e.g. MP4) - unlike embedded content which would be e.g. a YouTube link (see below for more information).

The download must be a dictionary object with the following fields:

1.1 Qualities

The field qualities should be an array of dictionaries defining different qualities provided by the site. The minimum required field is url which defines the URL to the download. Additional optional allowed fields are:

Here is a very simple example:

var download = {
    "qualities": [
        {
            "url": "https://www.example.com/file.mp4",
            "width": 1024,
            "height": 768,
            "headers": {
                "Referer": "https://www.example.com"
            }
        }
    ]
};

window.downie.reportDownload(download);
window.downie.reportDone();
1.2 Subtitles

The field subtitles of the download object, if populated, must contain an array of dictionaries and each dictionary must contain these two fields:

Example:

download.subtitles = [
    {
        "url": "https://www.example.com/subtitles.srt",
		"title": "en"
    }
]

2. Reporting an embedded download

Use window.downie.reportEmbeddedDownload(download) to report to Downie that you’ve found an embedded download, e.g. a YouTube or Vimeo link.

The download must be a dictionary object with the following fields:

3. Reporting you are done

This is very important - after you are done adding downloads, you should call window.downie.reportDone();. This tells Downie that you are done and it will process the downloads that you’ve reported. If you don’t do this, Downie will wait for a default timeout of 60 seconds before processing the results.

4. Reporting an error

Use window.downie.reportError("Something went wrong.") to report to Downie that the script failed to find what you were looking for. This causes Downie to close the web view and display the error in the UI.

5. Logging

Use window.downie.log("Something") to have Downie log something into the debug log. To enable the debug log, see the Debug menu in the menu bar.

6. Using Context

Since Downie v4.6.1, the window.downie object will contain a field context. This will be a dictionary that may contain the referer field (which contains the referring URL - e.g. if link from your custom integration gets extracted from http://www.example.com, then the referer field will contain this link).

Additionally, this can serve for passing information between integrations. If you have one integration that handles e.g. playlists and one that handles individual videos, you can pass information about the playlist to the integrations that handle individual videos:

// Playlist:
for (...) {
	var download = { ... };
	download.context = {
		"playlist": "Some Playlist",
		"index": 200,
		"count": 400
	};
	downie.reportEmbeddedDownload(download);
}
// Individual video:

var playlistName = downie.context.playlist;
// ...

You are absolutely free to use any keys you like, this is fully up to you, but note that the referer field will be overridden by Downie.

Testing

You can open a Console window in Preferences > Custom Integrations and see the logged content (see above). Note that if the download fails, you should remove the download from the queue and add it again after modifying the source code. Retrying a download that’s already in the queue will run the original code.

Real-World Example

Here is a simple real-world example (and this example is actually usable on quite a few sites). More examples can be found in the GitHub repo for custom integrations.

Example URL: http://www-db.deis.unibo.it/courses/TW/DOCS/w3schools/html/html5_video.asp.html (let’s assume the page has a video on each webpage like this) Integration Regex: https?://[^/]*unibo\.it/courses/.*/html/.*

This webpage uses HTML <video> tags and <source> tags inside, so you locate them using document.getElementsByTagName and extract the URL.

function getDownload() {
	var elements = document.getElementsByTagName("source");
	if (elements.length == 0) {
		// No <source> tags found.
		window.downie.reportError("No video elements.");
		return null;
	}	

	var url = elements[0].src;
	if (url == null || url == "") {
		// The source value is not loaded, or doesn't exist.
		window.downie.reportError("No video URL in video element.");
		return null;
	}	

	var download = {
		"qualities": [
			{
				"url": url
			}
		],
		"title": "Example Download",
		"preview": elements[0].parentElement.poster
	};
	return download;
}

var download = getDownload();
if (download != null) {
	window.downie.reportDownload(download);
	window.downie.reportDone();
}